{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Dictionary based time series classification in sktime\n",
    "\n",
    "Dictionary based approaches adapt the bag of words model commonly used in signal processing, computer vision and audio processing for time series classification.\n",
    "Dictionary based classifiers have the same broad structure.\n",
    "A sliding window of length $w$ is run across a series.\n",
    "For each window, the real valued series of length $w$ is converted through approximation and discretisation processes into a symbolic string of length $l$, which consists of $\\alpha$ possible letters.\n",
    "The occurrence in a series of each 'word' from the dictionary defined by $l$ and $\\alpha$ is counted, and once the sliding window has completed the series is transformed into a histogram.\n",
    "Classification is based on the histograms of the words extracted from the series, rather than the raw data.\n",
    "\n",
    "Currently 4 univeriate dictionary based classifiers are implemented in sktime, all making use of the Symbolic Fourier Approximation (SFA)\\[1\\] transform to discretise into words.\n",
    "These are the Bag of SFA Symbols (BOSS)\\[2\\], the Contractable Bag of SFA Symbols (cBOSS)\\[3\\], Word Extraction for Time Series Classification  (WEASEL)\\[4\\] and the Temporal Dictionary Ensemble (TDE)\\[5\\]. WEASEL has a multivariate extension called MUSE\\[7\\] and TDE has multivariate capabilities.\n",
    "\n",
    "In this notebook, we will demonstrate how to use BOSS, cBOSS, WEASEL and TDE on the ItalyPowerDemand and BasicMotions datasets.\n",
    "\n",
    "#### References:\n",
    "\n",
    "\\[1\\] Schäfer, P., & Högqvist, M. (2012). SFA: a symbolic fourier approximation and index for similarity search in high dimensional datasets. In Proceedings of the 15th International Conference on Extending Database Technology (pp. 516-527).\n",
    "\n",
    "\\[2\\] Schäfer, P. (2015). The BOSS is concerned with time series classification in the presence of noise. Data Mining and Knowledge Discovery, 29(6), 1505-1530.\n",
    "\n",
    "\\[3\\] Middlehurst, M., Vickers, W., & Bagnall, A. (2019). Scalable dictionary classifiers for time series classification. In International Conference on Intelligent Data Engineering and Automated Learning (pp. 11-19). Springer, Cham.\n",
    "\n",
    "\\[4\\] Schäfer, P., & Leser, U. (2017). Fast and accurate time series classification with WEASEL. In Proceedings of the 2017 ACM on Conference on Information and Knowledge Management (pp. 637-646).\n",
    "\n",
    "\\[5\\] Middlehurst, M., Large, J., Cawley, G., & Bagnall, A. (2020). The Temporal Dictionary Ensemble (TDE) Classifier for Time Series Classification. In The European Conference on Machine Learning and Principles and Practice of Knowledge Discovery in Databases.\n",
    "\n",
    "\\[6\\] Large, J., Bagnall, A., Malinowski, S., & Tavenard, R. (2019). On time series classification with dictionary-based classifiers. Intelligent Data Analysis, 23(5), 1073-1089.\n",
    "\n",
    "\\[7\\] Schäfer, P., & Leser, U. (2018). Multivariate time series classification with WEASEL+MUSE. 3rd ECML/PKDD Workshop on AALTD.\n",
    "\n",
    "## 1. Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-12-19T14:30:10.723956Z",
     "iopub.status.busy": "2020-12-19T14:30:10.723432Z",
     "iopub.status.idle": "2020-12-19T14:30:11.681151Z",
     "shell.execute_reply": "2020-12-19T14:30:11.681692Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn import metrics\n",
    "\n",
    "from sktime.classification.dictionary_based import (\n",
    "    MUSE,\n",
    "    WEASEL,\n",
    "    BOSSEnsemble,\n",
    "    ContractableBOSS,\n",
    "    TemporalDictionaryEnsemble,\n",
    ")\n",
    "from sktime.datasets import load_basic_motions, load_italy_power_demand"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## 2. Load data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-12-19T14:30:11.686582Z",
     "iopub.status.busy": "2020-12-19T14:30:11.686095Z",
     "iopub.status.idle": "2020-12-19T14:30:12.406787Z",
     "shell.execute_reply": "2020-12-19T14:30:12.407326Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(67, 1) (67,) (50, 1) (50,)\n",
      "(20, 6) (20,) (20, 6) (20,)\n"
     ]
    }
   ],
   "source": [
    "X_train, y_train = load_italy_power_demand(split=\"train\", return_X_y=True)\n",
    "X_test, y_test = load_italy_power_demand(split=\"test\", return_X_y=True)\n",
    "X_test = X_test[:50]\n",
    "y_test = y_test[:50]\n",
    "\n",
    "print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)\n",
    "\n",
    "X_train_mv, y_train_mv = load_basic_motions(split=\"train\", return_X_y=True)\n",
    "X_test_mv, y_test_mv = load_basic_motions(split=\"test\", return_X_y=True)\n",
    "\n",
    "X_train_mv = X_train_mv[:20]\n",
    "y_train_mv = y_train_mv[:20]\n",
    "X_test_mv = X_test_mv[:20]\n",
    "y_test_mv = y_test_mv[:20]\n",
    "\n",
    "print(X_train_mv.shape, y_train_mv.shape, X_test_mv.shape, y_test_mv.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## 3. Bag of SFA Symbols (BOSS)\n",
    "\n",
    "BOSS is an ensemble of individual BOSS classifiers making use of the SFA transform.\n",
    "The classifier performs grid-search through a large number of individual classifiers for parameters $l$, $\\alpha$, $w$ and $p$ (normalise each window).\n",
    "Of the classifiers searched only those within 92\\% accuracy of the best classifier are retained.\n",
    "Individual BOSS classifiers use a non-symmetric distance function, BOSS distance, in conjunction with a nearest neighbour classifier.\n",
    "\n",
    "As tuning is handled inside the classifier, BOSS has very little parameters to be altered and generally should be run using default settings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-12-19T14:30:12.411079Z",
     "iopub.status.busy": "2020-12-19T14:30:12.410605Z",
     "iopub.status.idle": "2020-12-19T14:30:13.198883Z",
     "shell.execute_reply": "2020-12-19T14:30:13.199360Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "BOSS Accuracy: 0.94\n"
     ]
    }
   ],
   "source": [
    "boss = BOSSEnsemble(random_state=47)\n",
    "boss.fit(X_train, y_train)\n",
    "\n",
    "boss_preds = boss.predict(X_test)\n",
    "print(\"BOSS Accuracy: \" + str(metrics.accuracy_score(y_test, boss_preds)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## 4. Contractable BOSS (cBOSS)\n",
    "\n",
    "cBOSS significantly speeds up BOSS with no significant difference in accuracy by improving how the ensemble is formed.\n",
    "cBOSS utilises a filtered random selection of parameters to find its ensemble members.\n",
    "Each ensemble member is built on a 70% subsample of the train data, using random sampling without replacement.\n",
    "An exponential weighting scheme for the predictions of the base classifiers is introduced.\n",
    "\n",
    "A new parameter for the number of parameters samples $k$ is introduced. of which the top $s$ (max ensemble size) with the highest accuracy are kept for the final ensemble.\n",
    "The $k$ parameter is replaceable with a time limit $t$ through contracting."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-12-19T14:30:13.210856Z",
     "iopub.status.busy": "2020-12-19T14:30:13.207136Z",
     "iopub.status.idle": "2020-12-19T14:30:14.650104Z",
     "shell.execute_reply": "2020-12-19T14:30:14.649632Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cBOSS Accuracy: 0.96\n"
     ]
    }
   ],
   "source": [
    "# Recommended non-contract cBOSS parameters\n",
    "cboss = ContractableBOSS(n_parameter_samples=250, max_ensemble_size=50, random_state=47)\n",
    "\n",
    "# cBOSS with a 1 minute build time contract\n",
    "# cboss = ContractableBOSS(time_limit_in_minutes=1,\n",
    "#                         max_ensemble_size=50,\n",
    "#                         random_state=47)\n",
    "\n",
    "cboss.fit(X_train, y_train)\n",
    "\n",
    "cboss_preds = cboss.predict(X_test)\n",
    "print(\"cBOSS Accuracy: \" + str(metrics.accuracy_score(y_test, cboss_preds)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## 5. Word Extraction for Time Series Classification (WEASEL)\n",
    "\n",
    "WEASEL transforms time series into feature vectors, using a sliding-window approach, which are then analyzed through a machine learning classifier. The novelty of WEASEL lies in its specific method for deriving features, resulting in a much smaller yet much more discriminative feature set than BOSS. It extends SFA by bigrams, feature selection using Anova-f-test and Information Gain Binning (IGB).\n",
    "\n",
    "### Univariate"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-12-19T14:30:14.656633Z",
     "iopub.status.busy": "2020-12-19T14:30:14.656058Z",
     "iopub.status.idle": "2020-12-19T14:30:15.042508Z",
     "shell.execute_reply": "2020-12-19T14:30:15.042998Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WEASEL Accuracy: 0.96\n"
     ]
    }
   ],
   "source": [
    "weasel = WEASEL(binning_strategy=\"equi-depth\", anova=False, random_state=47)\n",
    "weasel.fit(X_train, y_train)\n",
    "\n",
    "weasel_preds = weasel.predict(X_test)\n",
    "print(\"WEASEL Accuracy: \" + str(metrics.accuracy_score(y_test, weasel_preds)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Multivariate\n",
    "\n",
    "WEASEL+MUSE (Multivariate Symbolic Extension) is the multivariate extension of WEASEL."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MUSE Accuracy: 1.0\n"
     ]
    }
   ],
   "source": [
    "muse = MUSE()\n",
    "muse.fit(X_train_mv, y_train_mv)\n",
    "\n",
    "muse_preds = muse.predict(X_test_mv)\n",
    "print(\"MUSE Accuracy: \" + str(metrics.accuracy_score(y_test_mv, muse_preds)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Temporal Dictionary Ensemble (TDE)\n",
    "\n",
    "TDE aggregates the best components of 3 classifiers extending from the original BOSS algorithm. The ensemble structure and improvements of cBOSS\\[3\\] are used; Spatial pyramids are introduced from Spatial BOSS (S-BOSS)\\[6\\]; From Word Extraction for Time Series Classification (WEASEL)\\[4\\] bigrams and Information Gain Binning (IGB), a replacement for the multiple coefficient binning (MCB) used by SFA, are included.\n",
    "Two new parameters are included in the ensemble parameter search, the number of spatial pyramid levels $h$ and whether to use IGB or MCB $b$.\n",
    "A Gaussian processes regressor is used to select new parameter sets to evaluate for the ensemble, predicting the accuracy of a set of parameter values using past classifier performances.\n",
    "\n",
    "Inheriting the cBOSS ensemble structure, the number of parameter samples $k$, time limit $t$ and max ensemble size $s$ remain as parameters to be set accounting for memory and time requirements.\n",
    "\n",
    "### Univariate"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2020-12-19T14:30:15.049119Z",
     "iopub.status.busy": "2020-12-19T14:30:15.048625Z",
     "iopub.status.idle": "2020-12-19T14:30:24.886051Z",
     "shell.execute_reply": "2020-12-19T14:30:24.886568Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TDE Accuracy: 1.0\n"
     ]
    }
   ],
   "source": [
    "# Recommended non-contract TDE parameters\n",
    "tde_u = TemporalDictionaryEnsemble(\n",
    "    n_parameter_samples=50,\n",
    "    max_ensemble_size=50,\n",
    "    randomly_selected_params=50,\n",
    "    random_state=47,\n",
    ")\n",
    "\n",
    "# TDE with a 1 minute build time contract\n",
    "# tde = TemporalDictionaryEnsemble(time_limit_in_minutes=1,\n",
    "#                                 max_ensemble_size=50,\n",
    "#                                 randomly_selected_params=50,\n",
    "#                                 random_state=47)\n",
    "\n",
    "tde_u.fit(X_train, y_train)\n",
    "\n",
    "tde_u_preds = tde_u.predict(X_test)\n",
    "print(\"TDE Accuracy: \" + str(metrics.accuracy_score(y_test, tde_u_preds)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "### Multivariate"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TDE Accuracy: 1.0\n"
     ]
    }
   ],
   "source": [
    "# Recommended non-contract TDE parameters\n",
    "tde_mv = TemporalDictionaryEnsemble(\n",
    "    n_parameter_samples=50,\n",
    "    max_ensemble_size=50,\n",
    "    randomly_selected_params=50,\n",
    "    random_state=47,\n",
    ")\n",
    "\n",
    "# TDE with a 1 minute build time contract\n",
    "# tde_m = TemporalDictionaryEnsemble(time_limit_in_minutes=1,\n",
    "#                                 max_ensemble_size=50,\n",
    "#                                 randomly_selected_params=50,\n",
    "#                                 random_state=47)\n",
    "\n",
    "tde_mv.fit(X_train_mv, y_train_mv)\n",
    "\n",
    "tde_mv_preds = tde_mv.predict(X_test_mv)\n",
    "print(\"TDE Accuracy: \" + str(metrics.accuracy_score(y_test_mv, tde_mv_preds)))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
